
package com.jf.cloud.flow.service.impl;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.StrUtil;
import com.jf.cloud.api.auth.bo.UserInfoInTokenBO;
import com.jf.cloud.common.cache.constant.CacheNames;
import com.jf.cloud.common.constant.Constant;
import com.jf.cloud.common.security.AuthUserContext;
import com.jf.cloud.flow.constant.FlowLogPageEnum;
import com.jf.cloud.flow.constant.FlowVisitEnum;
import com.jf.cloud.flow.dto.FlowLogDTO;
import com.jf.cloud.flow.service.FlowService;
import com.jf.cloud.flow.service.ProductAnalyseService;
import com.jf.cloud.flow.service.UserAnalysisService;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.BoundListOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import java.util.*;
import java.util.concurrent.TimeUnit;

/**
 * 流量分析
 *
 * @author zz
 * @date 2021-05-21 15:25:19
 */
@Service
public class FlowServiceImpl implements FlowService {

    private static final Logger log = LoggerFactory.getLogger(FlowServiceImpl.class);

    @Autowired
    private RedisTemplate redisTemplate;
    @Autowired
    private RedissonClient redissonClient;
    @Autowired
    private ProductAnalyseService productAnalyseService;
    @Autowired
    private UserAnalysisService userAnalysisService;


    @Override
    public void log(FlowLogDTO flowLogDTO) {
        flowLogDTO.setCreateTime(DateUtil.beginOfDay(new Date()));
        UserInfoInTokenBO userInfoInTokenBO = AuthUserContext.get();
        // 登陆后，插入用户id
        if (Objects.nonNull(userInfoInTokenBO)) {
            flowLogDTO.setUserId(userInfoInTokenBO.getUserId().toString());
        } else {
            flowLogDTO.setUserId(null);
        }
        if (FlowVisitEnum.isVisitOrShare(flowLogDTO.getVisitType()) || Objects.equals(flowLogDTO.getVisitType(), FlowVisitEnum.CLICK.value())) {
            flowLogDTO.setNums(1);
        }
        // 用户数据
        redisTemplate.boundListOps(CacheNames.FLOW_FLOW_DATA).rightPush(flowLogDTO);

        // 商品数据
        if (Objects.equals(flowLogDTO.getPageId(), FlowLogPageEnum.PROD_INFO.value()) || Objects.equals(flowLogDTO.getVisitType(), FlowVisitEnum.SHOP_CAT.value())) {
            redisTemplate.boundListOps(CacheNames.FLOW_PRODUCT_DATA).rightPush(flowLogDTO);
        }
    }

    @Override
    public void statisticalUser() {
        RLock rLock = redissonClient.getLock( "statisticalUser");
        try {
            int lockWait = 10;
            if (rLock.tryLock(lockWait,lockWait, TimeUnit.SECONDS)){
                // 成功获取锁就处理下面的流程，没有获取到就证明已经在统计了，不需要重复统计
                // 数据只需要统计一次，所以多次统计请求，只要执行一次即可
                BoundListOperations boundListOperations = redisTemplate.boundListOps(CacheNames.FLOW_FLOW_DATA);
                Long size = boundListOperations.size();
                if (Objects.isNull(size)) {
                    return;
                }
                long maxIndex;
                while (size > 0) {
                    if (size > Constant.MAX_DATA_HANDLE_NUM) {
                        maxIndex = Constant.MAX_DATA_HANDLE_NUM;
                    } else {
                        maxIndex = size;
                    }
                    size = size - Constant.MAX_DATA_HANDLE_NUM;
                    //            // 获取范围内的用户操作信息
                    List<FlowLogDTO> flowLogList = boundListOperations.range(0, maxIndex -1);
                    // 订单延迟半个小时处理
                    handlePayData(flowLogList, boundListOperations);
                    userAnalysisService.statisticalUser(flowLogList);
                    // 统计成功后，删除已统计的信息
                    boundListOperations.trim(maxIndex, -1);
                }

            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            try {
                if (rLock.isLocked()) {
                    rLock.unlock();
                }
            }catch (Exception e){
                e.printStackTrace();
            }
        }
    }

    @Override
    public void statisticalProduct() {
        RLock rLock = redissonClient.getLock( "statisticalProduct");
        try {
            int lockWait = 10;
            // 成功获取锁就处理下面的流程，没有获取到就证明已经在统计了，不需要重复统计
            // 数据只需要统计一次，所以多次统计请求，只要执行一次即可
            if (rLock.tryLock(lockWait,lockWait, TimeUnit.SECONDS)){
                BoundListOperations boundListOperations = redisTemplate.boundListOps(CacheNames.FLOW_PRODUCT_DATA);
                Long size = boundListOperations.size();
                if (Objects.isNull(size)) {
                    return;
                }
                long maxIndex;
                while (size > 0) {
                    if (size > Constant.MAX_DATA_HANDLE_NUM) {
                        maxIndex = Constant.MAX_DATA_HANDLE_NUM;
                    } else {
                        maxIndex = size;
                    }
                    size = size - Constant.MAX_DATA_HANDLE_NUM;
                    List<FlowLogDTO> flowLogList = boundListOperations.range(0, maxIndex -1);
                    if (CollUtil.isEmpty(flowLogList)) {
                        continue;
                    }
                    Iterator<FlowLogDTO> iterator = flowLogList.iterator();
                    while (iterator.hasNext()) {
                        FlowLogDTO flowLogDTO = iterator.next();
                        // 过滤掉不符合规则的userid&&uuid的数据
                        boolean flag = StrUtil.hasBlank(flowLogDTO.getUserId()) || !Objects.equals(flowLogDTO.getUserId().length(), 12);
                        if (StrUtil.isNotBlank(flowLogDTO.getUserId()) && flag) {
                            // 用户id是12位的并且不包括空格
                            iterator.remove();
                            continue;
                        }
                        flag = StrUtil.hasBlank(flowLogDTO.getUuid()) || !flowLogDTO.getUuid().matches(Constant.UUID_PATTERN);
                        if (StrUtil.isNotBlank(flowLogDTO.getUuid()) && flag) {
                            // uuid正则校验
                            iterator.remove();
                        }
                    }
                    productAnalyseService.statisticalProduct(flowLogList);
                    boundListOperations.trim(maxIndex, -1);
                }
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            try {
                if (rLock.isLocked()) {
                    rLock.unlock();
                }
            }catch (Exception e){
                e.printStackTrace();
            }
        }
    }
    /**
     * 处理支付的信息
     * @param flowLogList
     * @param boundListOperations
     */
    private void handlePayData(List<FlowLogDTO> flowLogList, BoundListOperations boundListOperations) {
        if (CollUtil.isEmpty(flowLogList)) {
            return;
        }
        long time = DateUtil.offsetMinute(new Date(), -Constant.CANCEL_ORDER).getTime();
        List<FlowLogDTO> handleList = new ArrayList<>();
        Iterator<FlowLogDTO> iterator = flowLogList.iterator();
        while (iterator.hasNext()) {
            // 会出现数据类型为数组而并非flowLogDTO，导致类型转换报错，所以先做类型判断，再进行转换
            Object item = iterator.next();
            boolean instance = item instanceof FlowLogDTO;
            if (!instance) {
                iterator.remove();
                continue;
            }
            FlowLogDTO flowLogDTO = (FlowLogDTO) item;
            boolean flag = StrUtil.hasBlank(flowLogDTO.getUserId()) || !Objects.equals(flowLogDTO.getUserId().length(), 12);
            // 过滤掉不符合规则的userid&&uuid的数据
            if (StrUtil.isNotBlank(flowLogDTO.getUserId()) && flag) {
                // 用户id是12位的并且不包括空格
                iterator.remove();
                continue;
            }
            flag = StrUtil.hasBlank(flowLogDTO.getUuid()) || !flowLogDTO.getUuid().matches(Constant.UUID_PATTERN);
            if (StrUtil.isNotBlank(flowLogDTO.getUuid()) && flag) {
                // uuid正则校验
                iterator.remove();
                continue;
            }
            // 订单信息需要等取消订单处理完后，再进行处理
            if (Objects.equals(flowLogDTO.getPageId(), FlowLogPageEnum.PAY.value()) && flowLogDTO.getCreateTime().getTime() > time) {
                iterator.remove();
                handleList.add(flowLogDTO);
            }
        }
        if (CollUtil.isNotEmpty(handleList)) {
            boundListOperations.rightPushAll(handleList.toArray());
        }
    }
}
